package org.codefilarete.stalactite.query.model;

import java.util.Iterator;
import java.util.Map;

import org.codefilarete.stalactite.query.model.Selectable.SimpleSelectable;
import org.codefilarete.tool.collection.KeepOrderMap;
import org.codefilarete.tool.collection.KeepOrderSet;
import org.codefilarete.tool.reflect.MethodDispatcher;

/**
 * A support for the select part of a SQL query
 *
 * @author Guillaume Mary
 */
public class Select implements FluentSelect<Select> {
	
	/** Items in select and their aliases (null value means no alias) */
	private final KeepOrderMap<Selectable<?>, String> columns;
	
	private boolean distinct;
	
	public Select() {
		this.columns = new KeepOrderMap<>();
		this.distinct = false;
	}
	
	public Select(Select another) {
		this.columns = new KeepOrderMap<>(another.columns);
		this.distinct = another.distinct;
	}
	
	@Override
	public KeepOrderSet<Selectable<?>> getColumns() {
		return new KeepOrderSet<>(columns.keySet());
	}
	
	@Override
	public Select add(Iterable<? extends Selectable<?>> expressions) {
		expressions.forEach(this::add);
		return this;
	}
	
	@Override
	public Select add(Selectable<?> expression, Selectable<?>... expressions) {
		add(expression);
		for (Selectable<?> expr : expressions) {
			add(expr);
		}
		return this;
	}
	
	@Override
	public AliasableExpression<Select> add(String expression, Class<?> javaType) {
		SimpleSelectable<?> selectable = new SimpleSelectable<>(expression, javaType);
		add(selectable);
		return new MethodDispatcher()
				.redirect(Aliasable.class, alias -> {
					columns.put(selectable, alias);
					return null;	// we don't care about the returned object since a proxy is returned
				}, this)
				.redirect(SelectChain.class, this)
				.build((Class<AliasableExpression<Select>>) (Class<?>) AliasableExpression.class);
	}
	
	@Override
	public Select add(Selectable<?> column, String alias) {
		this.columns.put(column, alias);
		return this;
	}
	
	@Override
	public Select add(Map<? extends Selectable<?>, String> aliasedColumns) {
		this.columns.putAll(aliasedColumns);
		return this;
	}
	
	public Select remove(Selectable<?> selectable) {
		this.columns.remove(selectable);
		return this;
	}
	
	/**
	 * Gives column aliases. If a {@link Selectable} refers to a null value in returned {@link Map}, it means it has no alias
	 * 
	 * @return {@link Selectable} aliases of this instance
	 */
	@Override
	public Map<Selectable<?>, String> getAliases() {
		return new KeepOrderMap<>(columns);
	}
	
	public boolean isDistinct() {
		return distinct;
	}
	
	@Override
	public Select distinct() {
		this.distinct = true;
		return this;
	}
	
	public void setDistinct(boolean distinct) {
		this.distinct = distinct;
	}
	
	public KeepOrderMap<Selectable<?>, String> clear() {
		KeepOrderMap<Selectable<?>, String> result = new KeepOrderMap<>(this.columns);
		this.columns.clear();
		return result;
	}
	
	@Override
	public Iterator<Selectable<?>> iterator() {
		return this.columns.keySet().iterator();
	}
}
